package jflowmap.views.heatmap;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

import com.google.common.collect.Lists;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventListener;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;

public class PInfo extends PPath {
	private static final long serialVersionUID = 1L;
	
	public enum Position {
		TOP_LEFT, TOP_CENTER, TOP_RIGHT,
		BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT
	}
	
	private final PCamera camera;
	private final List<PNode> infoKeys = Lists.newArrayList();
	private final List<PNode> infoValues = Lists.newArrayList();
	private final List<String> infoIndices = Lists.newArrayList();
	
	private double maxKeyWidth = 0;
	private double maxValueWidth = 0;
	private Position position = Position.TOP_LEFT;
	
	public final static double ROW_HEIGHT = 14;
	public final static Font INFO_KEY_FONT = new Font("Arial", Font.BOLD, 10);
	public final static Font INFO_VALUE_FONT = new Font("Arial", Font.PLAIN, 10);
	public final static Stroke INFO_STROKE = new PFixedWidthStroke(2);
	public final static Paint INFO_PAINT = new Color(240, 240, 240, 230);
	
	public PInfo (PCamera camera) {
		this.camera = camera;
		camera.addPropertyChangeListener(new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if (evt.getPropertyName().equals(PCamera.PROPERTY_BOUNDS)) {
					PInfo.this.updatePosition();
				}
			}
		});
		
		this.setStroke(INFO_STROKE);
		this.setPaint(INFO_PAINT);
	}
	
	public void setPosition (Position pos) {
		this.position = pos;
		updatePosition();
	}
	
	public void add (String name, String value) {
		infoIndices.add(name);
		
		final PText nameNode = new PText(name);
		if (nameNode.getWidth() > maxKeyWidth) {
			maxKeyWidth = nameNode.getWidth();
		}
		nameNode.setFont(INFO_KEY_FONT);
		infoKeys.add(nameNode);
		this.addChild(nameNode);
		
		final PText valueNode = new PText(value);
		if (valueNode.getWidth() > maxValueWidth) {
			maxValueWidth = valueNode.getWidth();
		}
		valueNode.setFont(INFO_VALUE_FONT);
		valueNode.setName(value);
		infoValues.add(valueNode);
		this.addChild(valueNode);
		
		this.updatePosition();
	}
	
	public void set (String name, String value) {
		if (infoIndices.contains(name)) {
			final int idx = infoIndices.indexOf(name);
			
			final PNode valueNode = infoValues.get(idx);
			this.removeChild(valueNode);
			
			final PText newValueNode = new PText(value);
			if (newValueNode.getWidth() > maxValueWidth) {
				maxValueWidth = newValueNode.getWidth();
			}
			newValueNode.setFont(INFO_VALUE_FONT);
			newValueNode.setName(value);
			for (PInputEventListener listener: valueNode.getInputEventListeners()) {
				newValueNode.addInputEventListener(listener);
			}
			
			infoValues.set(idx, newValueNode);
			this.addChild(newValueNode);
			
		} else {
			this.add(name, value);
		}
		
		this.calculateMaxWidths();
		this.updatePosition();
	}
	
	public void remove (String name) {
		if (infoIndices.contains(name)) {
			final int idx = infoIndices.indexOf(name);
			
			infoIndices.remove(idx);
			final PNode infoKey = infoKeys.remove(idx);
			final PNode infoValue = infoValues.remove(idx);
			
			this.removeChild(infoKey);
			this.removeChild(infoValue);
			
			this.calculateMaxWidths();
			this.updatePosition();
		}
	}
	
	public void onClick (final String key, final Runnable runnable) {
		if (infoIndices.indexOf(key) == -1) return;
				
		final PNode keyNode = infoKeys.get(infoIndices.indexOf(key));
		final PNode valueNode = infoValues.get(infoIndices.indexOf(key));
		
		PInputEventListener listener = new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				if (event.getClickCount() == 1) {
					runnable.run();
				}
			}
		};
		
		keyNode.addInputEventListener(listener);
		valueNode.addInputEventListener(listener);
	}
	
	private void calculateMaxWidths() {
		this.maxKeyWidth = 0;
		this.maxValueWidth = 0;
		for (int i = 0; i < infoIndices.size(); ++i) {
			final PNode key = infoKeys.get(i);
			final PNode value = infoValues.get(i);
			
			if (key.getWidth() > this.maxKeyWidth) {
				this.maxKeyWidth = key.getWidth();
			}
			
			if (value.getWidth() > this.maxValueWidth) {
				this.maxValueWidth = value.getWidth();
			}
		}
	}

	public void updatePosition() {
		final PBounds vb = getCamera().getBounds();
		
		double width = maxKeyWidth + maxValueWidth + 15;
		double height = infoKeys.size() * ROW_HEIGHT + 4;
		
		double x = vb.x + 
				 (position == Position.TOP_LEFT | position == Position.BOTTOM_LEFT ? 10
				 : position == Position.TOP_CENTER | position == Position.BOTTOM_CENTER ? vb.width/2 - width/2
				 : vb.width - width - 10);
		
		double y = vb.y + 
				   (position == Position.TOP_LEFT | position == Position.TOP_CENTER | position == Position.TOP_RIGHT ? 
				   10 : vb.height - height - 10);
		
		this.setBounds(x, y, width, height);
		this.setPathTo(new RoundRectangle2D.Double(x, y, width, height, 10, 10));
		
		double yPos = 4;
		for (int i = 0; i < infoIndices.size(); ++i) {
			final PNode key = infoKeys.get(i);
			final PNode value = infoValues.get(i);
			
			key.setY(y + yPos);
			value.setY(y + yPos);
			
			key.setX(x + 5);
			value.setX(x + maxKeyWidth + 10);
			
			yPos += ROW_HEIGHT;
		}
		
	}
	
	public PCamera getCamera() {
		return this.camera;
	}

	public String get(String key) {
		if (infoIndices.indexOf(key) == -1) return null;
		return this.infoValues.get(infoIndices.indexOf(key)).getName();
	}
	
}
